/******************************************************************************
*  Copyright(c) 2021-2023 Sietium
*
*  @file:    mesh.cpp
*  @brif:    mesh display
*
*  @date:    06/12/2021
*  @Author:  bird.du
*
******************************************************************************/
#include <memory>
#include "vmath.h"
#include "glad/gl-headers.h"

#include "glslprogram.h"
#include "node.h"
#include "model.h"
#include "objectfactory.h"

class Mesh: public Node
{
public:
    Mesh();
protected:
    bool startup();
    void render(double currentTime, double difTime);
    void shutdown();
protected:
    GLSLProgram program;
    std::unique_ptr<Model> model;
    float flagTime = 0;
    float minY, maxY;

    float prevTime = 0.0f;
    GLint currentPatchValue = 1;
};

Mesh::Mesh()
{
    nodeName = "Mesh";
}

bool Mesh::startup()
{
    if (PerfWindow::glVersion < 400) {
        printf("Failed! The GL version is less than 4.0 !\n");
        return false;
    }

    bool ok = program.build("../media/shaders/mesh/mesh.vs.glsl",
                            "../media/shaders/mesh/mesh.fs.glsl",
                            "../media/shaders/mesh/mesh.geom.glsl",
                            "../media/shaders/mesh/mesh.tc.glsl",
                            "../media/shaders/mesh/mesh.te.glsl");
    if (!ok)
        return false;

    model.reset(new Model);
    model->loadModel("../media/objects/animal.obj");

    auto minPos = model->getMinPosition();
    auto maxPos = model->getMaxPosition();

    minY = minPos[1];
    maxY = maxPos[1];

    float dif = maxY - minY;
    minY -= 0.1 * dif;
    maxY += 0.1 * dif;

    model->setUseGLPath(GL_TRUE);

#ifndef USE_GLES
    /* glLogicOp is only provided in GLES 1.0 */
    glEnable(GL_COLOR_LOGIC_OP);
    glLogicOp(GL_OR);
#endif
    glEnable(GL_DEPTH_TEST);

    glPatchParameteri(GL_PATCH_VERTICES, 3);

    flagTime = glfwGetTime();
    return true;
}

void Mesh::render(double currentTime, double difTime)
{
    (void)difTime;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    const GLfloat gray[] = { 0.0f, 0.0f, 0.2f, 1.0f };
    const GLfloat ones[] = { 1.0f };

    glClearBufferfv(GL_COLOR, 0, gray);
    glClearBufferfv(GL_DEPTH, 0, ones);

    glPatchParameteri(GL_PATCH_VERTICES, 3);

    program.use();

    float f = currentTime * 1.5f;

    auto modelMatrix = vmath::scale(42.0f);
    auto viewPosition = vmath::vec3(cosf(f * 0.35f) * 120.0f, cosf(f * 0.4f) * 1.0f,
                                     sinf(f * 0.35f) * 120.0f);
    auto viewMatrix = vmath::lookat(viewPosition, vmath::vec3(0.0f, .0f, 0.0f), vmath::vec3(0.0f,
                                                                                              1.0f, 0.0f));
    auto mvMatrix = viewMatrix * modelMatrix;
    auto projMatrix = vmath::perspective(45.0f, (float)getWidth() / (float)getHeight(),
                                          0.1f,
                                          1000.0f);

    program.setUniformMatrix4fv("mv_matrix", mvMatrix);
    program.setUniformMatrix4fv("proj_matrix", projMatrix);

    program.setUniform1f("value", sin(glfwGetTime()) * 0.5);

    if (currentTime - prevTime > 3.0f) {
        currentPatchValue ++;
        if (currentPatchValue == 3)
            currentPatchValue = 1;
        prevTime = currentTime;
    }

    program.setUniform1f("tc_outer0", currentPatchValue);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
#ifndef USE_GLES
    /* GL_FILL is an inherent mode for GLES to draw polygons. No need to set GL_FILL.*/
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif

    model->renderModel();
}

void Mesh::shutdown()
{
    glBindVertexArray(0);

#ifndef USE_GLES
    glDisable(GL_COLOR_LOGIC_OP);
#endif
    model->clearModel();
    program.clear();

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
#ifndef USE_GLES
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
    glUseProgram(0);
}

REGISTER_OBJECT(Node, Mesh)
